package wavenotes.robot;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.jdo.PersistenceManager;

import org.json.JSONArray;
import org.json.JSONException;

import wavenotes.model.PMF;
import wavenotes.model.preferences.UserPreference;
import wavenotes.model.presentation.Note;
import wavenotes.model.presentation.Presentation;
import wavenotes.model.presentation.Slide;
import wavenotes.model.presentation.Wave;

import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.wave.api.AbstractRobot;
import com.google.wave.api.Blip;
import com.google.wave.api.BlipContentRefs;
import com.google.wave.api.Element;
import com.google.wave.api.ElementType;
import com.google.wave.api.FormElement;
import com.google.wave.api.Gadget;
import com.google.wave.api.Image;
import com.google.wave.api.Line;
import com.google.wave.api.Restriction;
import com.google.wave.api.Wavelet;
import com.google.wave.api.event.FormButtonClickedEvent;
import com.google.wave.api.event.WaveletParticipantsChangedEvent;
import com.google.wave.api.event.WaveletSelfAddedEvent;
import com.google.wave.api.impl.EventMessageBundle;

import flexjson.JSONDeserializer;

@Singleton
public class NewRobotServlet extends AbstractRobot
{
	// Static states
	private static class GadgetState
	{
		public static final String Selection = "selection";
		public static final String PreStart = "preStart";
		public static final String Started = "started";
	}

	private static class FormAction
	{
		public static final String StartPresentation = "StartPresentation";
		public static final String SyndicatePresentation = "SyndicatePresentation";
	}

	// Static fields
	private static final long serialVersionUID = -8121992617921351435L;
	private static final String PresentationGadgetUrl = "http://wave-notes.appspot.com/GoogleWaveGadget.xml";
	private static final String GadgetStateVar_PresentationList = "listOfPresentations";
	private static final String GadgetStateVar_PreferredParticipants = "preferredParticipants";
	private static final String GadgetStateVar_CurrentParticipants = "currentParticipants";
	private static final String GadgetStateVar_PresentationKey = "presentationKey";
	private static final String GadgetStateVar_PresentationState = "presentationState";
	private static final String GadgetStateVar_Presentation = "presentation";
	private static String PREVIEW_DOMAIN = "googlewave.com";
	private static String PREVIEW_RPC_URL = "http://gmodules.com/api/rpc";
	private static String OAUTH_KEY = null;
	private static String OAUTH_SECRET = null;

	// Member fields
	private PersistenceManager pm = null;
	private Injector injector = null;

	@Inject
	public NewRobotServlet(Injector injector)
	{
		this.injector = injector;
		OAUTH_KEY = System.getProperty("OAUTH_KEY");
		OAUTH_SECRET = System.getProperty("OAUTH_SECRET");

		initOauth();
	}

	public void initOauth()
	{
		setupOAuth(OAUTH_KEY, OAUTH_SECRET, PREVIEW_RPC_URL);
		setAllowUnsignedRequests(true);
	}

//	/**
//	 * Active robot stuff to update a presentation in the gadget
//	 */
//	public void updateGadgetPresentation(Presentation presentation) throws IOException, JSONException
//	{
//		log("update gadgetpresentation " + presentation.getTitle());
//
//		log("presentation waves: " + presentation.getWaves().size());
//		if (null != presentation.getWaves())
//		{
//			for (Wave wave : presentation.getWaves())
//			{
//				log("update - " + wave.getWaveId() + ", " + wave.getWaveletId());
//
//				try
//				{
//					Wavelet wavelet = fetchWavelet(WaveId.deserialise(wave.getWaveId()), WaveletId.deserialise(wave.getWaveletId()), PREVIEW_RPC_URL);
//
//					log("got a wave - " + wavelet);
//					BlipContentRefs ref = wavelet.getRootBlip().first(ElementType.GADGET, Restriction.of(Gadget.URL, PresentationGadgetUrl));
//					HashMap<String, String> properties = new HashMap<String, String>();
//					properties.put(GadgetStateVar_Presentation, presentation.toJSON().toString());
//					ref.updateElement(properties);
//					wavelet.submitWith(wavelet); // not sure if this needed
//				}
//				catch (Exception e)
//				{
//					log("update problem with " + wave.getWaveId(), e);
//				}
//			}
//		}
//	}

	@Override
	protected void processEvents(EventMessageBundle events)
	{
		try
		{
			pm = PMF.get().getPersistenceManager();
			// Don't override anything. Just need this event to create
			// PersistenceManager. Method in parent class will take care of
			// event passing
			super.processEvents(events);
		}
		catch (Exception ex_p)
		{
			this.log(ex_p.getMessage(), ex_p);
		}
		finally
		{
			if (pm != null)
				pm.close();
		}
	}

	@Override
	protected String getRobotName()
	{
		return "WaveNotes";
	}

	@Override
	protected String getRobotAvatarUrl()
	{
		return "http://wave-notes.appspot.com/images/robot_image.png";
	}

	/**
	 * Handles the event where the robot was added to the wave
	 */
	@Override
	public void onWaveletSelfAdded(WaveletSelfAddedEvent event)
	{
		Wavelet wavelet = event.getWavelet();
		Blip blip = wavelet.getRootBlip();

		// See if gadget already exists
		for (Element element : blip.getElements().values())
		{
			if (element.isGadget() && ((Gadget) element).getUrl().equals(PresentationGadgetUrl))
				return;
		}

		// Add gadget
		Gadget gadget = new Gadget(PresentationGadgetUrl);
		gadget.setProperty(GadgetStateVar_PresentationList, Presentation.getPresentationList(wavelet, pm));
		UserPreference userPreference = UserPreference.getUserPreference(wavelet.getCreator(), pm);
		if (userPreference != null)
		{
			gadget.setProperty(GadgetStateVar_PreferredParticipants, userPreference.getParticipantList());
			gadget.setProperty(GadgetStateVar_CurrentParticipants, userPreference.getParticipantList());
		}
		blip.append(gadget);
		this.log("New wave by " + wavelet.getCreator());

		// Add form button
		FormElement formElement = new FormElement(ElementType.BUTTON, FormAction.StartPresentation, "Start Presentation");
		blip.append(formElement);
	}

	/**
	 * Handles the change in participants
	 */
	@Override
	public void onWaveletParticipantsChanged(WaveletParticipantsChangedEvent event)
	{
		/*
		BlipContentRefs ref = event.getBlip().first(ElementType.GADGET, Restriction.of(Gadget.URL, PresentationGadgetUrl));
		Gadget gadget = (Gadget) ref.value();
		JSONArray currentParticipants = null;

		this.log("Handling participants changed");
		UserPreference userPreference = UserPreference.getUserPreference(event.getWavelet().getCreator(), pm);
		if (userPreference == null)
		{
			userPreference = new UserPreference(event.getWavelet().getCreator(), "[]");
			userPreference = pm.makePersistent(userPreference);
		}
		try
		{
			if (gadget.getProperty(GadgetStateVar_CurrentParticipants) == null)
				currentParticipants = new JSONArray();
			else
				currentParticipants = new JSONArray(gadget.getProperty(GadgetStateVar_CurrentParticipants));
			JSONArray participantArray = new JSONArray(userPreference.getParticipantList());
			for (String participant : event.getParticipantsAdded())
			{
				if (!jsonArrayContains(participantArray, participant) && !participant.equals("wave-notes@appspot.com"))
				{
					participantArray.put(participant);
					currentParticipants.put(participant);
				}
			}
			userPreference.setParticipantList(participantArray.toString());
		}
		catch (JSONException e)
		{
			this.log(e.getMessage(), e);
		}
		HashMap<String, String> properties = new HashMap<String, String>();
		properties.put(GadgetStateVar_PreferredParticipants, userPreference.getParticipantList());
		properties.put(GadgetStateVar_CurrentParticipants, currentParticipants.toString());
		ref.updateElement(properties);
		this.log("Participants changed to " + userPreference.getParticipantList());
		*/
	}

	private boolean jsonArrayContains(JSONArray array, String value) throws JSONException
	{
		for (int i = 0; i < array.length(); i++)
		{
			if (array.getString(i).equals(value))
				return true;
		}
		return false;
	}

	/**
	 * Handles form buttons being clicked
	 */
	@Override
	public void onFormButtonClicked(FormButtonClickedEvent event)
	{
		this.log("Processing form button: " + event.getButtonName());

		if (event.getButtonName().equals(FormAction.StartPresentation))
		{
			processStartPresentation(event);
		}
		else if (event.getButtonName().equals(FormAction.SyndicatePresentation))
		{
			processSyndication(event);
		}
		else
		{
			this.log("Unknown Form Button: " + event.getButtonName());
		}
	}

	private List<SortedSet<Note>> getWaveNotes(Gadget gadget, Wavelet wave, Presentation presentation)
	{
		List<SortedSet<Note>> peopleNotes = new ArrayList<SortedSet<Note>>(presentation.getSlides().size());
		
		for (int i=0; i<presentation.getSlides().size(); i++) {
			peopleNotes.add(new TreeSet<Note>());
		}

		for (String participant : wave.getParticipants()) {
			String json = gadget.getProperty(participant + "_notes");
			
			if (null != json && json.length() > 0) {
				Map<String, ArrayList<Note>> personNotes = new JSONDeserializer<Map<String, ArrayList<Note>>>()
				.use(null, HashMap.class)
				.use("values", ArrayList.class)
				.use("values.values", Note.class)
				.deserialize(json);
				
				for (String index : personNotes.keySet()) {
					try {
						int i = Integer.parseInt(index.substring(index.lastIndexOf("_") + 1));
						
						for (Note note : personNotes.get(index)) {
							if (note != null)
								peopleNotes.get(i).add(note);
						}
					} catch (Exception e) {
						this.log("problem with getting notes for " + index + ", " + participant, e);
					}
				}
			}
		}
		
		return peopleNotes;
	}

	private void processSyndication(FormButtonClickedEvent event)
	{
		this.log("do syndications");

		BlipContentRefs ref = event.getBlip().first(ElementType.GADGET, Restriction.of(Gadget.URL, PresentationGadgetUrl));
		Gadget gadget = (Gadget) ref.value();

		PersistenceManager pm = PMF.get().getPersistenceManager();
		Presentation presentation = pm.getObjectById(Presentation.class, gadget.getProperty(GadgetStateVar_PresentationKey));

		Wavelet currentWave = event.getWavelet();

		List<SortedSet<Note>> notes = getWaveNotes(gadget, currentWave, presentation);

		
		Wavelet syndicateWave = this.newWave(currentWave.getDomain(), event.getWavelet().getParticipants());
		syndicateWave.setTitle(currentWave.getTitle() + " - syndicate wave");
		Blip currentBlip = syndicateWave.getRootBlip();
		int i = 0;
		for (Slide slide : presentation.getSlides())
		{
			currentBlip = currentBlip.reply();
			currentBlip.append(new Image(slide.getSlideUrl(), 400, 300, "slidy"));
			for (Note note : notes.get(i))
			{
				currentBlip.append("\n" + note.getDisplayName() + ": " + note.getText());
			}

			i++;
		}

		syndicateWave.submitWith(currentWave);
	}

	private void processStartPresentation(FormButtonClickedEvent event)
	{
		BlipContentRefs ref = event.getBlip().first(ElementType.GADGET, Restriction.of(Gadget.URL, PresentationGadgetUrl));
		Gadget gadget = (Gadget) ref.value();

		String presentationKey = gadget.getProperty(GadgetStateVar_PresentationKey);
		String preferredParticipants = gadget.getProperty(GadgetStateVar_PreferredParticipants);
		String currentParticipantString = gadget.getProperty(GadgetStateVar_CurrentParticipants);
		
		this.log("processing start presentation. key="+presentationKey+"; pref="+preferredParticipants+"; curr="+currentParticipantString+";");

		if (presentationKey == null)
		{
			ref = event.getBlip().first(ElementType.BUTTON, Restriction.of(FormElement.NAME, FormAction.StartPresentation));
			HashMap<String, String> properties = new HashMap<String, String>();
			properties.put(FormElement.VALUE, "Start Presentation (Select Presentation First)");
			ref.updateElement(properties);
		}
		else
		{
			try
			{
				// Sanitize participants and add them to the wave
				JSONArray preferredParticipantArray = new JSONArray(preferredParticipants);
				JSONArray cleanedParticipants = new JSONArray();
				JSONArray currentParticipantArray;
				
				if (currentParticipantString == null)
					currentParticipantArray = new JSONArray();
				else
					currentParticipantArray = new JSONArray(currentParticipantString);
				this.log("Current participants: "+cleanedParticipants.toString());

				for (int i = 0; i < preferredParticipantArray.length(); i++)
				{
					String participantId = preferredParticipantArray.getJSONObject(i).getString("id");
					if (!preferredParticipantArray.isNull(i) && participantId != null && !participantId.equals(""))
					{
						cleanedParticipants.put(preferredParticipantArray.get(i).toString());
					}
				}
				
				for (int i = 0; i < currentParticipantArray.length(); i++)
				{
					if (!currentParticipantArray.isNull(i) && !currentParticipantArray.get(i).toString().equals(""))
					{
						event.getWavelet().getParticipants().add(currentParticipantArray.get(i).toString());
					}
				}

				// Stored the sanitized version of the participant list
				UserPreference userPreference = UserPreference.getUserPreference(event.getWavelet().getCreator(), pm);
				if (userPreference != null)
					userPreference.setParticipantList(cleanedParticipants.toString());
				else
				{
					userPreference = new UserPreference(event.getWavelet().getCreator(), cleanedParticipants.toString());
					pm.makePersistent(userPreference);
				}

				// Set the gagdet state to started
				HashMap<String, String> properties = new HashMap<String, String>();
				properties.put(GadgetStateVar_PresentationState, GadgetState.Started);
				ref.updateElement(properties);

				// Remove start presentation button
				ref = event.getBlip().first(ElementType.BUTTON, Restriction.of(FormElement.NAME, FormAction.StartPresentation));
				if (ref.value().asElement().isFormElement())
				{
					ref.delete();
				}
				
				//add syndication button
				FormElement formElement = new FormElement(ElementType.BUTTON, FormAction.SyndicatePresentation, "Syndicate Presentation");
				event.getBlip().append(formElement);
			}
			catch (Exception ex_p)
			{
				this.log(ex_p.getMessage(), ex_p);
			}
		}
	}
}
